package com.gitee.qdbp.base.tags;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitee.qdbp.tools.files.PathTools;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 标签设置<br>
 * append 配置: None or Version or LastModified or string<br>
 * prepend 配置: RelativePath or AbsolutePath or SiteFullPath or http://res.xxx.com/<br>
 * 使用时根据extension查询group(res/img/...), 再根据group查询prepend和append<br>
 * serverType: default|dynamic|static|hybrid, 取自request.getHeader("Server-Type")<br>
 * default=默认, dynamic=JSP请求, static=导出用于静态化, hybrid=导出用于混合应用(action请求用完整URL)<br>
 * <pre>
 * assets.setting.res = extension:js|css, prepend:RelativePath, append:Version, hybrid.append:None
 * assets.setting.img = extension:bmp|jpg|jpeg|png|gif, prepend:http://img.xxx.com/
 * assets.setting.vdo = extension:mp4|ogg|3pg|avi|mpg, prepend:http://vdo.xxx.com/
 * assets.setting.action = extension:json|do, prepend:AbsolutePath, hybrid.prepend:SiteFullPath
 * assets.setting.page = extension:NULL|PATH|shtml, prepend:RelativePath, hybrid.prepend:SiteFullPath
 * assets.setting.other = extension:*, prepend:RelativePath
 * </pre>
 *
 * @author zhaohuihua
 * @version 181026
 * @since v1.0
 */
public class TagSettings {

    private static final Logger log = LoggerFactory.getLogger(TagSettings.class);

    private static String SETTING_PREFIX = "assets.setting.";

    /** 配置内容缓存 **/
    private Map<String, Setting> settings = new HashMap<>();
    /** 默认前缀 **/
    private PrependType defaultPrependType = PrependType.AbsolutePath;
    /** 默认后缀 **/
    private AppendType defaultAppendType = AppendType.None;
    /** 默认设置 **/
    private Setting defaultSetting = new Setting(defaultPrependType, defaultAppendType);
    /** 判断后缀是否出现在多个的配置项中 **/
    // key = serverType.extension, value = configKeys
    // 如: default.json = [assets.setting.action]
    Map<String, List<String>> extensionForKeys = new HashMap<>();

    /** 获取当前请求的serverType: default|dynamic|static|hybrid, 取自request.getHeader("Server-Type") **/
    public String getServerType(HttpServletRequest request) {
        return request.getHeader("Server-Type");
    }

    public String getUriExtension(String uri) {
        if (uri != null && uri.endsWith("/")) {
            return "PATH";
        }
        String extension = PathTools.getExtension(uri);
        return extension == null ? "NULL" : extension.toLowerCase();
    }

    /** 构造函数 **/
    public TagSettings(Properties properties) {
        load(properties);
    }

    /** 获取所有设置信息 **/
    public Map<String, Setting> getAllSettings() {
        return this.settings;
    }

    /** 根据urlExtension获取设置信息 **/
    public Setting getSetting(String urlExtension) {
        return getSetting(null, urlExtension);
    }

    /** 根据serverType+urlExtension获取设置信息 **/
    public Setting getSetting(String serverType, String urlExtension) {
        Map<String, Setting> settings = this.settings;
        String defServerType = "default";
        String realServerType = VerifyTools.isBlank(serverType) ? defServerType : serverType;
        String realExtension = VerifyTools.isBlank(urlExtension) ? "NULL" : urlExtension;
        // ServerType.extension
        Setting setting = settings.get(StringTools.concat('.', realServerType, realExtension));
        if (setting == null && !realServerType.equals(defServerType)) {
            // default.extension
            setting = settings.get(StringTools.concat('.', defServerType, realExtension));
        }
        if (setting == null) {
            // ServerType.*
            setting = settings.get(StringTools.concat('.', realServerType, "*"));
        }
        return setting == null ? defaultSetting : setting;
    }

    private void load(Properties properties) {
        // 兼容旧版本
        // # 静态文件后缀, 这些文件追加版本号
        // assets.suffix = .js|.css
        String assetsSuffixKey = "assets.suffix";
        String assetsSuffixValue = properties.getProperty(assetsSuffixKey);
        if (VerifyTools.isNotBlank(assetsSuffixValue)) {
            Map<String, String> map = new HashMap<>();
            map.put("extension", assetsSuffixValue);
            map.put("prepend", "AbsolutePath");
            map.put("append", "Version");
            load(map, assetsSuffixKey, assetsSuffixValue);
        }

        // 遍历, 筛选前缀为assets.setting的配置项, 解析并加载到缓存中
        Iterator<Entry<Object, Object>> iterator = properties.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<Object, Object> entry = iterator.next();
            if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
                String key = (String) entry.getKey();
                if (key.startsWith(SETTING_PREFIX)) {
                    String value = (String) entry.getValue();
                    Map<String, String> map = parseSettingValue(value);
                    load(map, key, value);
                }
            }
        }

        // 判断后缀是否出现在多个的配置项中
        for (Entry<String, List<String>> entry : extensionForKeys.entrySet()) {
            String extension = entry.getKey();
            List<String> keys = entry.getValue();
            if (keys.size() > 1) {
                String m = "Extension appearing in more than one key, extension={}, keys={}";
                log.warn(m, extension, JsonTools.toJsonString(keys));
            }
        }
    }

    // 示例: assets.setting.action = extension:json|do, prepend:AbsolutePath, hybrid.prepend:SiteFullPath
    // 则map = {extension=json|do, prepend=AbsolutePath, hybrid.prepend=SiteFullPath}
    // key = assets.setting.action
    // value = extension:json|do, prepend:AbsolutePath, hybrid.prepend:SiteFullPath
    private void load(Map<String, String> map, String key, String value) {
        String extensionValues = map.get("extension");
        if (VerifyTools.isBlank(extensionValues)) {
            log.warn("Failed to parse setting value, extension is required, key={}, value: {}", key, value);
            return;
        }

        // extension:json|do, prepend:AbsolutePath, hybrid.prepend:SiteFullPath
        // 其中prepend:AbsolutePath用于default
        String[] serverTypes = StringTools.split("default|dynamic|static|hybrid");
        String[] extensions = splitExtensions(extensionValues);
        for (String serverType : serverTypes) {
            // 获取prepend和append
            String prependKey = "default".equals(serverType) ? "prepend" : serverType + "." + "prepend";
            String prependValue = map.get(prependKey);
            String appendKey = "default".equals(serverType) ? "append" : serverType + "." + "append";
            String appendValue = map.get(appendKey);
            if (VerifyTools.isAllBlank(prependValue, appendValue)) {
                continue;
            }
            // 解析PrependType
            PrependType prependType = defaultPrependType;
            try {
                if (VerifyTools.isNotBlank(prependValue)) {
                    prependType = parsePrependType(prependValue);
                }
            } catch (IllegalArgumentException e) {
                String m = "Failed to parse prepend type, key={}, use default value: {}, {}";
                log.warn(m, prependKey, prependType.name(), e.getMessage());
            }
            // 解析append
            AppendType appendType = defaultAppendType;
            try {
                if (VerifyTools.isNotBlank(appendValue)) {
                    appendType = parseAppendType(appendValue);
                }
            } catch (IllegalArgumentException e) {
                String m = "Failed to parse append type, key={}, use default value: {}, {}";
                log.warn(m, appendKey, appendType.name(), e.getMessage());
            }
            // 构造Setting对象
            String prependString = null;
            if (prependType == PrependType.StaticString) {
                prependString = prependValue;
            }
            String appendString = null;
            if (appendType == AppendType.StaticString) {
                appendString = appendValue;
            }
            Setting setting = new Setting(prependType, prependString, appendType, appendString);
            // 导入缓存中
            for (String extension : extensions) {
                if (VerifyTools.isNotBlank(extension)) {
                    String cacheKey = StringTools.concat('.', serverType, extension);
                    this.settings.put(cacheKey, setting);
                    if (this.extensionForKeys.containsKey(cacheKey)) {
                        this.extensionForKeys.get(cacheKey).add(key);
                    } else {
                        this.extensionForKeys.put(cacheKey, ConvertTools.toList(key));
                    }
                }
            }
        }
    }

    /** 以,分隔多个字段, 以:分隔Key和Value, 解析为Map结构 **/
    // extension:js|css, prepend:RelativePath, append:Version, hybrid.append:None
    // extension:json|do, prepend:AbsolutePath, hybrid.prepend:SiteFullPath
    // extension:mp4|ogg|3pg|avi|mpg, prepend:http://vdo.xxx.com/
    private static Map<String, String> parseSettingValue(String string) {
        Map<String, String> map = new HashMap<>();
        char[] chars = string.toCharArray();
        String key = null;
        StringBuilder buffer = new StringBuilder();
        for (int i = 0, len = chars.length; i < len; i++) {
            char c = chars[i];
            if (c == ':' && key == null) {
                key = buffer.toString().trim();
                buffer.setLength(0);
            } else if (c == ',') {
                map.put(key, buffer.toString().trim());
                key = null;
                buffer.setLength(0);
            } else {
                buffer.append(c);
            }
        }
        if (key != null) {
            map.put(key, buffer.toString().trim());
            key = null;
            buffer.setLength(0);
        }
        return map;
    }

    /** 拆分后缀名, 如果带点则把点去掉. 如: .js|.css = [js, css] **/
    private static String[] splitExtensions(String extensions) {
        String[] array = StringTools.split(extensions);
        List<String> list = new ArrayList<>();
        for (String item : array) {
            if (VerifyTools.isNotBlank(item)) {
                if (item.charAt(0) == '.') {
                    list.add(item.substring(1));
                } else {
                    list.add(item);
                }
            }
        }
        return ConvertTools.toArray(list, String.class);
    }

    private static PrependType parsePrependType(String prependValue) {
        if (VerifyTools.isBlank(prependValue)) {
            return null;
        }
        // RelativePath or AbsolutePath or SiteFullPath
        for (PrependType item : PrependType.values()) {
            if (item.name().equals(prependValue)) {
                return item;
            }
        }
        return PrependType.StaticString;
    }

    private static AppendType parseAppendType(String appendValue) {
        if (VerifyTools.isBlank(appendValue)) {
            return null;
        }
        // None or Version
        for (AppendType item : AppendType.values()) {
            if (item.name().equals(appendValue)) {
                return item;
            }
        }
        return AppendType.StaticString;
    }

    /** 前缀类型 **/
    public static enum PrependType {
        /** 相对路径(..开头的相对路径) **/
        RelativePath,
        /** 绝对路径(/开头的绝对路径) **/
        AbsolutePath,
        /** 网站全路径(http开头的全路径) **/
        SiteFullPath,
        /** 静态文本 **/
        StaticString
    }

    /** 后缀类型 **/
    public static enum AppendType {
        /** 无 **/
        None,
        /** 版本号 **/
        Version,
        /** 修改时间 **/
        LastModified,
        /** 静态文本 **/
        StaticString
    }

    public static class Setting {

        /** 前缀类型 **/
        private final PrependType prependType;
        /** 前缀静态文本 **/
        private final String prependString;
        /** 后缀类型 **/
        private final AppendType appendType;
        /** 后缀静态文本 **/
        private final String appendString;

        public Setting(PrependType prependType) {
            this(prependType, null, AppendType.None, null);
        }

        public Setting(PrependType prependType, String prependString) {
            this(prependType, prependString, AppendType.None, null);
        }

        public Setting(PrependType prependType, AppendType appendType) {
            this(prependType, null, appendType, null);
        }

        public Setting(PrependType prependType, String prependString, AppendType appendType) {
            this(prependType, prependString, appendType, null);
        }

        public Setting(PrependType prependType, AppendType appendType, String appendString) {
            this(prependType, null, appendType, appendString);
        }

        public Setting(PrependType prependType, String prependString, AppendType appendType, String appendString) {
            super();
            this.prependType = prependType;
            this.prependString = prependString;
            this.appendType = appendType;
            this.appendString = appendString;
        }

        /** 前缀类型 **/
        public PrependType getPrependType() {
            return prependType;
        }

        /** 前缀静态文本 **/
        public String getPrependString() {
            return prependString;
        }

        /** 后缀类型 **/
        public AppendType getAppendType() {
            return appendType;
        }

        /** 后缀静态文本 **/
        public String getAppendString() {
            return appendString;
        }

    }
}
